﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;

using GeneralTool.CoreLibrary.Interfaces;
using GeneralTool.CoreLibrary.Logs;
using GeneralTool.CoreLibrary.SocketLib;

namespace GeneralTool.CoreLibrary.TaskLib
{
    /// <summary>
    /// 包服务客户端,只适用于 <see cref="PackageServerStation"/> 创建的服务
    /// </summary>
    public class PackageStationClient
    {
        private readonly ILog log;
        private readonly Encoding encoding;
        private readonly Dictionary<Type, PackageConverter> converters;

        /// <summary>
        /// 超时时间 
        /// </summary>
        public int TimeOutMillSeconds { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="log">日志组件</param>
        /// <param name="jsonConvert">Json转换器组件</param>
        /// <param name="encoding">编码格式</param>
        /// <param name="packageHelper">包转换器组件</param>
        /// <param name="timeOutMillSeconds">读取超时时间</param>
        public PackageStationClient(ILog log = null, IJsonConvert jsonConvert = null, Encoding encoding = null, PackageHelper packageHelper = null, int timeOutMillSeconds = 0)
        {
            if (log == null) log = new ConsoleLogInfo();
            if (jsonConvert == null) jsonConvert = new BaseJsonCovert();
            if (encoding == null) encoding = Encoding.UTF8;
            if (packageHelper == null) packageHelper = new PackageHelper(jsonConvert, encoding);

            this.log = log;
            this.encoding = encoding;
            this.converters = packageHelper.GetConverters();
            this.TimeOutMillSeconds = timeOutMillSeconds;
        }

        /// <summary>
        /// 调用远程服务方法
        /// </summary>
        /// <typeparam name="T">要返回的类型</typeparam>
        /// <param name="ip">远程服务IP</param>
        /// <param name="port">远程服务端口</param>
        /// <param name="url">远程服务类url</param>
        /// <param name="method">当前调用的远程服务方法</param>
        /// <param name="args">当前调用的远程服务参数,必须与远程服务的参数数量和名称保持一致</param>
        /// <returns></returns>
        public T Invoke<T>(string ip, int port, string url, string method, Dictionary<string, object> args)
        {
            return (T)this.Invoke(ip, port, url, method, args, typeof(T));
        }

        /// <summary>
        /// 调用远程服务方法
        /// </summary>
        /// <param name="ip">远程服务IP</param>
        /// <param name="port">远程服务端口</param>
        /// <param name="url">远程服务类url</param>
        /// <param name="method">当前调用的远程服务方法</param>
        /// <param name="args">当前调用的远程服务参数,必须与远程服务的参数数量和名称保持一致</param>
        /// <param name="returnType">要返回的类型</param>
        /// <returns></returns>
        public object Invoke(string ip, int port, string url, string method, Dictionary<string, object> args, Type returnType)
        {
            if (args == null) args = new Dictionary<string, object>();
            using (var tcpClient = new TcpClient())
            {
                tcpClient.Connect(ip, port);
                using (var stream = tcpClient.GetStream())
                {
                    this.Request(url, method, args, stream);

                    return this.Reponse(stream, returnType);
                }
            }
        }

        private object Reponse(NetworkStream stream, Type returnType)
        {
            //获取返回
            var len_data = stream.ReadBytes(4, this.TimeOutMillSeconds);
            var len = BitConverter.ToInt32(len_data, 0);

            var data_arr = stream.ReadBytes(len, this.TimeOutMillSeconds);
            //解析返回
            using (var ms = new MemoryStream(data_arr))
            {
                using (var reader = new BinaryReader(ms))
                {
                    var success = reader.ReadBoolean(); //方法是否调用成功

                    //获取反回值异常信息
                    data_arr = this.ReadBuffer(reader);
                    var msg = this.encoding.GetString(data_arr); //调用返回异常信息

                    if (!success) throw new Exception(msg);

                    //解析返回值
                    if (returnType == typeof(void)) return null;

                    //读取返回类型,因为都是C#的,所以不用管了
                    _ = ReadBuffer(reader);

                    //读取返回值
                    data_arr = this.ReadBuffer(reader);
                    if (!this.converters.TryGetValue(returnType, out var converter))
                    {
                        converter = this.converters[typeof(object)];
                        this.log.Waring($"当前要返回的类型为 {returnType},但未注册此类型解析器,使用object解析器");
                    }

                    var obj = converter.BufferToObjectFunc(data_arr, returnType);
                    return obj;
                }
            }
        }

        private byte[] ReadBuffer(BinaryReader reader)
        {
            var len = reader.ReadInt32();
            var data_arr = reader.ReadBytes(len);
            return data_arr;
        }

        private void Request(string url, string method, Dictionary<string, object> args, NetworkStream stream)
        {
            //方法调用 
            using (var ms = new MemoryStream())
            {
                using (var writer = new BinaryWriter(ms))
                {
                    writer.Write(0); //占位长度

                    var urlStr = $"{url}{method}";
                    var buffer = this.encoding.GetBytes(urlStr);
                    writer.Write(buffer.Length);
                    writer.Write(buffer);

                    //参数列表 
                    var pstr = string.Join(",", args.Keys);
                    buffer = this.encoding.GetBytes(pstr);
                    writer.Write(buffer.Length);
                    writer.Write(buffer);

                    //每个参数值的写入
                    foreach (var item in args)
                    {
                        var itemType = item.Value.GetType();
                        if (!this.converters.TryGetValue(itemType, out var converter))
                        {
                            this.log.Waring($"没有找到参数 {item.Key} 值 {item.Value} 的转换器,使用object转换器");
                            converter = this.converters[typeof(object)];
                        }

                        buffer = converter.ObjectToBufferFunc(item.Value);
                        writer.Write(buffer.Length);
                        writer.Write(buffer);
                    }
                    //写入总长度
                    ms.Seek(0, SeekOrigin.Begin);
                    writer.Write((int)ms.Length - 4);
                }
                //发送给服务端
                var bytes = ms.ToArray();
                stream.Write(bytes, 0, bytes.Length);
            }
        }
    }
}
